BERT 原理与代码解析

论文:BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding

从输入层(Embedding),编码层(Transformer-Encoder)和输出层三部分组成讲解。

模型结构

输入层

和 Transformer 的 Encoder 区别很大:

编码层

BERT 仅仅使用 transformer 的 encoder,没有什么特殊的地方:

预训练

BERT 主要的工作就是提出了这两种预训练任务,MLM 和 NSP

Task 1#: Masked LM

首先,使用 [MASK] 随机 mask 掉 15% 的 token,但是考虑预测中,模型是遇不到 [MASK] 的,所以为了避免影响模型输出概率,当选定一个待 mask 的词时,使用如下策略:

  1. 80% 的概率将其替换为 [MASK]
  2. 10% 的概率将其随机替换为其它 token
  3. 10% 的概率不改变它

做 MLM 训练时,就是将 mask 掉的 token 的最后一层隐藏层向量输入一个线性层,映射到整个词表,即得到了每个词的概率,损失函数用交叉熵。

核心代码如下:

class BertLMPredictionHead(nn.Module):
    def __init__(self, config):
        super().__init__()
        self.transform = BertPredictionHeadTransform(config)

        # The output weights are the same as the input embeddings, but there is
        # an output-only bias for each token.
        self.decoder = nn.Linear(config.hidden_size, config.vocab_size, bias=False)

        self.bias = nn.Parameter(torch.zeros(config.vocab_size))

        # Need a link between the two variables so that the bias is correctly resized with `resize_token_embeddings`
        self.decoder.bias = self.bias

    def forward(self, hidden_states):
        hidden_states = self.transform(hidden_states)
        hidden_states = self.decoder(hidden_states)
        return hidden_states

Task 2#: Next Sentence Prediction

因为涉及到 QA 和 NLI 之类的任务,增加了第二个预训练任务,目的是让模型理解两个句子之间的联系。训练的输入是句子 A 和 B,B 有一半的几率是 A 的下一句,输入这两个句子,模型预测 B 是不是 A 的下一句。

这就是一个二分类任务,使用 [CLS] 输入到一个线性层,然后做 softmax 即可。

注意: 后面 RoBERTa 的工作通过实验表明,NSP 任务是没有必要的。

微调

参数量计算

image.png

BERT 变种

RoBERTa(Robustly Optimized BERT Pre-training Approach)

DeBERTa(Decoding-enhanced BERT with disentangled attention)

DeBERTa V2